package main

import (
	"crypto/tls"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
	"log"
	"net/http"
	"net/http/httptrace"
	"net/url"
	"strconv"
	"strings"
	"time"
)

var (
	DNSStartTime             time.Time
	DNSDoneTime              time.Time
	ConnectStartTime         time.Time
	ConnectDoneTime          time.Time
	GotConnTime              time.Time
	GotFirstResponseByteTime time.Time
	TLSHandshakeStartTime    time.Time
	TLSHandshakeDoneTime     time.Time
	TotalTime                time.Time
	RemoteAddr               string
	StatusCode               int

	process = []string{"namelookup", "connect", "pretransfer", "total"}
)

type Data struct {
	Addr        string `json:"addr"`
	StatusCode  int    `json:"status_code"`
	ProcessTime map[string]string
}

type App struct {
	Port    int `yaml:"port"`
	TimeOut time.Duration `yaml:"timeout"`
}

type Config struct {
	App
}

var Configs = &Config{}

func parseURL(uri string, isHttps bool) *url.URL {
	if !strings.Contains(uri, "://") && !strings.HasPrefix(uri, "//") {
		uri = "//" + uri
	}
	url, err := url.Parse(uri)
	if err != nil {
		//log.Println(err)
		log.Fatal(err)
	}
	if url.Scheme == "" {
		if isHttps {
			url.Scheme = "https"
		} else {
			url.Scheme = "http"
		}
	}
	return url
}

func Detection(uri string, isHttps bool) *Data {
	url := parseURL(uri, isHttps)
	req, err := http.NewRequest("GET", url.String(), nil)
	trace := &httptrace.ClientTrace{
		DNSStart: func(info httptrace.DNSStartInfo) {
			DNSStartTime = time.Now()
		},
		DNSDone: func(info httptrace.DNSDoneInfo) {
			DNSDoneTime = time.Now()
		},
		ConnectStart: func(network, addr string) {
			ConnectStartTime = time.Now()
		},
		ConnectDone: func(network, addr string, err error) {
			ConnectDoneTime = time.Now()
		},
		GotConn: func(connInfo httptrace.GotConnInfo) {
			RemoteAddr = connInfo.Conn.RemoteAddr().String()
			GotConnTime = time.Now()
		},
		GotFirstResponseByte: func() {
			GotFirstResponseByteTime = time.Now()
		},
		TLSHandshakeStart: func() {
			TLSHandshakeStartTime = time.Now()
		},
		TLSHandshakeDone: func(state tls.ConnectionState, err error) {
			TLSHandshakeDoneTime = time.Now()
		},
	}
	if err != nil {
		log.Printf(" err: %v", err)
	}

	req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

	// 配置超时时间
	client := &http.Client{Timeout: Configs.TimeOut * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		log.Println(err)
	}

	TotalTime = time.Now()
	StatusCode = resp.StatusCode
	return parseDetection()

}

func fmtb(d time.Duration) string {
	return strconv.Itoa(int(d))+"ms"

}

func parseDetection() *Data {
	result := make(map[string]string)
	// 如果探测的 host 是ip 没有lookup 过程
	if DNSStartTime.IsZero() {
		DNSStartTime = ConnectStartTime
	}

	fmtb := func(d time.Duration) string {
		return strconv.Itoa(int(d/time.Millisecond))+"ms"
	}

	result["namelookup"] = fmtb(DNSDoneTime.Sub(DNSStartTime))
	result["connect"] = fmtb(ConnectDoneTime.Sub(DNSStartTime))
	result["pretransfer"] = fmtb(GotFirstResponseByteTime.Sub(DNSStartTime))
	result["total"] = fmtb(TotalTime.Sub(DNSStartTime))

	return &Data{
		Addr:        RemoteAddr,
		StatusCode:  StatusCode,
		ProcessTime: result,
	}

}

func init() {
	viper.SetConfigFile("conf/app.yaml")
	viper.SetConfigType("yaml")
	err := viper.ReadInConfig()
	if err != nil {
		log.Fatalf("读取配置文件失败 : %s", err)
	}
	err = viper.Unmarshal(Configs)
	if err != nil {
		//fmt.Println(err)
		log.Fatalf("读取配置文件失败 ERROR: %v", err)
		return
	}
	fmt.Printf("%#v\n", Configs)

}

func main() {
	r := gin.Default()
	r.GET("/probe/http", func(c *gin.Context) {

		host := c.Query("host")
		isHttps, _ := strconv.ParseBool(c.Query("is_https"))

		data := Detection(host, isHttps)
		c.JSON(200, gin.H{
			"data": data,
		})
	})
	r.Run(fmt.Sprintf(":%d", Configs.Port)) // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
